﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MetaDrive.CodePress
{
    public enum AccessibilityLevel
    {
        Public,
        Protected,
        Internal,
        ProtectedInternal,
        Private
    }

    public enum OverloadType
    {
        None,
        New,
        Override,
        Abstract
    }

    /// <summary>
    /// Class used for clean, formatted generation of c# code;
    /// </summary>
    public class CodeBuilder
    {
        protected StringBuilder _sb;

        protected List<string> _namespaces;
        protected List<string> _bases;

        protected string _namespace;
        protected string _className;
        protected bool _partialClass;
        protected AccessibilityLevel _classAccessibility;

        protected int _indent;
        protected bool _singleLineIndent;

        protected bool _newline;

        protected AccessibilityLevel _defaultFieldAccessibility;
        protected AccessibilityLevel _defaultPropertyAccessibility;
        protected AccessibilityLevel _defaultConstructorAccessibility;
        protected AccessibilityLevel _defaultMethodAccessbility;
        
        public CodeBuilder(string @namespace, string className)
        {
            _sb = new StringBuilder();

            _namespaces = new List<string>();
            _bases = new List<string>();

            _namespace = @namespace;
            _className = className;
            _partialClass = true;
            _classAccessibility = AccessibilityLevel.Public;

            _indent = 2;    // starting at 2 since we are already in the class

            _singleLineIndent = false;
            _newline = true;        // starting off, we are always on a new line

            _defaultConstructorAccessibility = AccessibilityLevel.Public;
            _defaultFieldAccessibility = AccessibilityLevel.Protected;
            _defaultMethodAccessbility = AccessibilityLevel.Public;
            _defaultPropertyAccessibility = AccessibilityLevel.Public;
        }

        public int CurrentIndent
        {
            get { return _indent; }
        }

        public List<string> Namespaces
        {
            get { return _namespaces; }
        }

        public List<string> Bases
        {
            get { return _bases; }
        }

        public string ClassName
        {
            get { return _className; }
        }

        public string Namespace
        {
            get { return _namespace; }
        }

        public bool PartialClass
        {
            get { return _partialClass; }
            set { _partialClass = value; }
        }

        public AccessibilityLevel ClassAccessibility
        {
            get { return _classAccessibility; }
            set { _classAccessibility = value; }
        }

        public AccessibilityLevel DefaultConstructorAccessibility
        {
            get { return _defaultConstructorAccessibility; }
            set { _defaultConstructorAccessibility = value; }
        }

        public AccessibilityLevel DefaultFieldAccessibility
        {
            get { return _defaultFieldAccessibility; }
            set { _defaultFieldAccessibility = value; }
        }

        public AccessibilityLevel DefaultPropertyAccessbility
        {
            get { return _defaultPropertyAccessibility; }
            set { _defaultPropertyAccessibility = value; }
        }

        public AccessibilityLevel DefaultMethodAccessbility
        {
            get { return _defaultMethodAccessbility; }
            set { _defaultMethodAccessbility = value; }
        }

        public CodeBuilder Append(string text)
        {
            if (_newline)
            {
                AppendIndent();
                _newline = false;
            }

            _sb.Append(text);

            return this;
        }
        
        public CodeBuilder BeginBlock()
        {
            Append("{");
            NewLine();

            Indent();

            return this;
        }

        public CodeBuilder EndBlock()
        {
            Outdent();

            Append("}");
            NewLine();

            return this;
        }

        public CodeBuilder BeginRegion(string regionName)
        {
            Append("#region ");
            Append(regionName);
            NewLine();

            return this;
        }

        public CodeBuilder EndRegion()
        {
            Append("#endregion");
            NewLine();

            return this;
        }

        public CodeBuilder AppendField(string type, string fieldName)
        {
            return AppendField(_defaultFieldAccessibility, type, fieldName);
        }

        public CodeBuilder AppendField(AccessibilityLevel level, string type, string fieldName)
        {
            Append(GetAccessibilityLevel(level));
            Append(" ");
            Append(type);
            Append(" ");
            Append(fieldName);
            Append(";");
            NewLine();

            return this;
        }

        public CodeBuilder BeginConstructor()
        {
            return BeginConstructor(_defaultConstructorAccessibility, null);
        }

        public CodeBuilder BeginConstructor(AccessibilityLevel level)
        {
            return BeginConstructor(level, null);
        }

        public CodeBuilder BeginConstructor(AccessibilityLevel level, Dictionary<string, string> parameters)
        {
            Append(GetAccessibilityLevel(level));
            Append(" ");
            Append(_className);
            Append("(");

            if (parameters != null)
            {
                AppendParameters(parameters);
            }

            Append(")");
            NewLine();
            BeginBlock();

            return this;
        }

        public CodeBuilder EndConstructor()
        {
            EndBlock();

            return this;
        }

        public CodeBuilder BeginMethod(string returnType, string name)
        {
            return BeginMethod(_defaultMethodAccessbility, returnType, name, null);
        }

        public CodeBuilder BeginMethod(string returnType, string name, Dictionary<string, string> parameters)
        {
            return BeginMethod(_defaultMethodAccessbility, returnType, name, parameters);
        }

        public CodeBuilder BeginMethod(AccessibilityLevel level, string returnType, string name)
        {
            return BeginMethod(level, returnType, name, null);
        }

        public CodeBuilder BeginMethod(AccessibilityLevel level, string returnType, string name, Dictionary<string, string> parameters)
        {
            return BeginMethod(level, returnType, name, parameters, OverloadType.None);
        }

        public CodeBuilder BeginMethod(AccessibilityLevel level, string returnType, string name, Dictionary<string, string> parameters, OverloadType overload)
        {
            return BeginMethod(level, returnType, name, parameters, overload, false);
        }

        public CodeBuilder BeginMethod(AccessibilityLevel level, string returnType, string name, Dictionary<string, string> parameters, OverloadType overload, bool isStatic)
        {
            Append(GetAccessibilityLevel(level));
            Append(" ");
            if (overload == OverloadType.New)
            {
                Append("new ");
            }
            else if (overload == OverloadType.Override)
            {
                Append("override ");
            }

            if (isStatic)
                Append("static ");

            Append(returnType);
            Append(" ");
            Append(name);
            Append("(");
            if (parameters != null)
            {
                AppendParameters(parameters);
            }

            Append(")");
            NewLine();
            BeginBlock();

            return this;
        }

        public CodeBuilder EndMethod()
        {
            EndBlock();

            return this;
        }

        public CodeBuilder BeginProperty(string type, string name)
        {
            return BeginProperty(_defaultPropertyAccessibility, type, name);
        }

        public CodeBuilder BeginProperty(AccessibilityLevel level, string type, string name)
        {
            return BeginProperty(level, type, name, OverloadType.None);
        }

        public CodeBuilder BeginProperty(AccessibilityLevel level, string type, string name, OverloadType overload)
        {
            return BeginProperty(level, type, name, overload, false);
        }

        public CodeBuilder BeginProperty(AccessibilityLevel level, string type, string name, OverloadType overload, bool isStatic)
        {
            Append(GetAccessibilityLevel(level));
            Append(" ");
            if (overload == OverloadType.New)
            {
                Append("new ");
            }
            else if (overload == OverloadType.Override)
            {
                Append("override ");
            }

            if (isStatic)
                Append("static ");

            Append(type);
            Append(" ");
            Append(name);
            NewLine();
            BeginBlock();

            return this;
        }

        public CodeBuilder EndProperty()
        {
            EndBlock();

            return this;
        }

        public CodeBuilder BeginGet()
        {
            Append("get");
            NewLine();
            BeginBlock();

            return this;
        }

        public CodeBuilder EndGet()
        {
            EndBlock();

            return this;
        }

        public CodeBuilder AppendGet(string propertyName)
        {
            Append("get { return ");
            Append(propertyName);
            Append("; }");
            NewLine();

            return this;
        }

        public CodeBuilder BeginSet()
        {
            Append("set");
            NewLine();
            BeginBlock();

            return this;
        }

        public CodeBuilder EndSet()
        {
            EndBlock();

            return this;
        }

        public CodeBuilder Indent()
        {
            _indent++;

            return this;
        }

        public CodeBuilder SingleLineIndent()
        {
            Indent();
            _singleLineIndent = true;

            return this;
        }

        public CodeBuilder Outdent()
        {
            _indent--;

            return this;
        }

        public CodeBuilder NewLine()
        {
            return NewLine(false);
        }

        public CodeBuilder NewLine(bool closeStatement)
        {
            if (closeStatement)
                _sb.Append(";");

            _sb.AppendLine();
            _newline = true;

            if (_singleLineIndent)
                Outdent();

            _singleLineIndent = false;

            return this;
        }

        public CodeBuilder AddUsing(string namespaceName)
        {
            this.Namespaces.Add(namespaceName);
            return this;
        }

        public CodeBuilder AppendParameters(Dictionary<string, string> parameters)
        {
            bool first = true;
            foreach (KeyValuePair<string, string> kvp in parameters)
            {
                if (!first)
                {
                    Append(", ");
                }

                first = false;

                Append(kvp.Key);
                Append(" ");
                Append(kvp.Value);
            }

            return this;
        }

        public string GetText()
        {
            StringBuilder sb = new StringBuilder();

            foreach (string ns in _namespaces)
            {
                sb.Append("using ").Append(ns).AppendLine(";");
            }

            sb.AppendLine();

            sb.Append("namespace ").AppendLine(_namespace);
            sb.AppendLine("{");

            sb.Append("\t").Append(GetAccessibilityLevel(_classAccessibility));
            sb.Append(" ");
            if (_partialClass)
                sb.Append("partial ");

            sb.Append("class ");

            sb.Append(_className);

            if (_bases.Count > 0)
            {
                sb.Append(" : ");

                bool first = true;
                foreach (string bas in _bases)
                {
                    if (!first)
                        sb.Append(", ");

                    first = false;

                    sb.Append(bas);
                }
            }

            sb.AppendLine();
            sb.AppendLine("\t{");   // open class

            sb.Append(_sb.ToString());

            sb.AppendLine("\t}"); // close class
            sb.AppendLine("}");

            return sb.ToString();
        }

        protected void AppendIndent()
        {
            _sb.Append(new string('\t', _indent));
        }

        protected string GetAccessibilityLevel(AccessibilityLevel level)
        {
            switch (level)
            {
                case AccessibilityLevel.Public:
                    return "public";
                case AccessibilityLevel.Protected:
                    return "protected";
                case AccessibilityLevel.Internal:
                    return "internal";
                case AccessibilityLevel.ProtectedInternal:
                    return "protected internal";
                case AccessibilityLevel.Private:
                    return "private";
            }

            return "";
        }
    }
}
